package com.general.framework.example.mergeRequestWhenHighConcurrency;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/*
我们经常会写一些查询接口，假设现在我们需要写一个查询用户信息的接口，要求传入用户Id，返回用户名称。
那么最简化的流程就是：用户发送请求->controller层->service层->dao层->数据库。
每次请求就相当于请求一条用户信息。
当这个接口被用户频繁请求时，此接口就不断的在做“请求”到“返回”的操作，服务端同时会开辟许多线程帮我们执行这些操作，这么多的线程会消耗许多系统资源，服务端承受了巨大压力。
那么我们有什么方式可以优化这种操作呢？
我目前能想到的就是利用缓存（缓存热点数据）、消息队列（接收请求慢慢消费达到流量削峰）、多个服务实例（分散请求压力提高计算能力）等方式应对高并发场景。
在本文中，我利用另一种思路：把多个请求合并为一个请求，把单查询变为批量查询，这样就能有效减少开辟线程的数量。
*/

@RestController
public class QueryController {
    private UserService userService;
    // 存放请求的队列
    LinkedBlockingDeque<RequestTest> queue = new LinkedBlockingDeque<>();

    // 初始化方法
    @PostConstruct
    public void init() {
        // 定时执行的线程池，每隔5毫秒执行一次(间隔时间可以由业务决定)，把所有堆积的请求合并为一个请求
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        executorService.scheduleAtFixedRate(() -> {
            // 在这里具体执行批量查询逻辑
            int size = queue.size();
            if (size == 0) {
                // 若没有请求堆积，直接返回，等10毫秒再执行一次
                return;
            }
            // 若有请求堆积把所有请求都拿出来
            List<RequestTest> requestTests = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                // 把请求拿出来
                RequestTest poll = queue.poll();
                requestTests.add(poll);
            }
            // 至此请求已经被合并了
            System.out.printf("##############################################\n");
            System.out.printf("请求合并了" + requestTests.size() + "条！\n");
            // 组装批量查询条件
            List<Long> keyList = new ArrayList<>();
            for (RequestTest requestTest : requestTests) {
                keyList.add(requestTest.getKey());
            }
            // 进行批量查询
            List<User> nameList = userService.getUserList(keyList);
            // 把批查结果放入一个map
            Map<Long, String> map = new HashMap<>();
            for (User user : nameList) {
                map.put(user.getId(), user.getName());
            }
            for (RequestTest requestTest : requestTests) {
                // 把放在map中的结果集返给对应的线程
                // future是对应每个请求的，因为是每个请求线程都传了自己的future是对应的过来
                requestTest.getFuture().complete(map.get(requestTest.getKey()));
            }
        }, 0, 10, TimeUnit.MILLISECONDS);
    }

    // 单查询接口
    @GetMapping("/getUser")
    public String getUser(Long key) {
        long currentMillis = System.currentTimeMillis();
        // 单查询service，大量线程怼到这个service上去，导致系统资源被大量消耗；
        // 解决方法：把单查询变为批量查询，减少线程数量，提高系统资源利用率。
        String userName = "";
        // String userName = userService.getUser(key);
        System.out.printf("##############################################\n");
        System.out.printf("用户名为:" + userName + "---线程名为:" + Thread.currentThread().getName() +
                "---执行时间为:" + (System.currentTimeMillis() - currentMillis) + "\n");
        return userName;
    }

    // 请求合并
    @GetMapping("/requestMerge/getUser")
    public String getUserRequestMerge(Long key) throws InterruptedException, ExecutionException {
        long currentMillis = System.currentTimeMillis();
        // CompletableFuture可以使一个线程执行操作后，主动返回值给另一个线程
        CompletableFuture<String> future = new CompletableFuture<>();
        RequestTest requestTest = new RequestTest();
        // 把future(把future可以认为是线程间的"传话人")放到等待队列中去，让定时调度的线程池执行并返回值
        requestTest.setFuture(future);
        requestTest.setKey(key);
        // 把requestTest加入等待队列(LinkedBlockingDeque)
        queue.add(requestTest);
        // future(传话人)阻塞直到有值返回
        String userName = future.get();
        System.out.printf("用户名为:" + userName + "---线程名为:" + Thread.currentThread().getName() +
                "---执行时间为:" + (System.currentTimeMillis() - currentMillis) + "\n");
        return userName;
    }
}
